/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
	fprintf(stdout, "Bruinbase> ");
	
	// set the command line input and start parsing user input
	sqlin = commandline;
	sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
	// SqlParser.y by bison (bison is GNU equivalent of yacc)
	
	return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;   // RecordFile containing the table
	RecordId   rid;  // record cursor for table scanning
	BTreeIndex idx;	// index for the table
	
	RC     rc;
	int    key;     
	string value;
	int    count = 0;
	int    diff;
	int	 idxState = 0;
	
	// open the table file
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}
	
	vector<int> keyEqual;
	int keyMax = -1;
	int keyMin = -1;
	vector<int> keyNotEqual;
	int keyGreaterEqual = 0;
	int keyLessEqual = 0;
	bool multipleKeyEqual = false;
	bool multipleKeyNotEqual = false;
	bool hasNotEquals = false;
	bool isEquals = false;
	
	vector<char*> valEqual;
	char* valMax = NULL;
	char* valMin = NULL;
	vector<char*> valNotEqual;
	bool multipleValEqual = false;
	bool multipleValNotEqual = false;
	int valLessEqual = 0;
	int valGreaterEqual = 0;
	
	int condVal;
	
	// check the conditions on the tuple
	for (unsigned i = 0; i < cond.size(); i++) {
		
		condVal = atoi(cond[i].value);
	
		switch(cond[i].attr) {
				
				// Condition involving Key
			case 1:		
				switch (cond[i].comp) {
					case SelCond::EQ:
						if(keyEqual.size() != 0)	// this means there has been an equal condition already
						{
							multipleKeyEqual = true;
						}
						isEquals = true;
						keyEqual.push_back(condVal);
						break;
					case SelCond::NE:
						if(keyNotEqual.size() != 0)	// this means there has been an equal condition already
						{
							multipleKeyNotEqual = true;
						}
						hasNotEquals = true;
						keyEqual.push_back(condVal);
						break;
					case SelCond::LE:
						if(keyMax == -1 || condVal <= keyMax) 
						{
							keyMax = condVal;
							keyLessEqual = 1;
						}
						break;
					case SelCond::LT:
						if (keyMax == -1 || condVal < keyMax) 
						{
							keyMax = condVal;
							keyLessEqual = 0;
						}
						break;
					case SelCond::GE:
						if(keyMin == -1 || condVal >= keyMin) 
						{
							keyMin = condVal;
							keyGreaterEqual = 1;
						}
						break;
					case SelCond::GT:
						if (keyMin == -1 || condVal > keyMin) 
						{
							keyMin = condVal;
							keyGreaterEqual = 0;
						}
						break;
				}
				break;
				
				// Condition involving Value
			case 2:		
				switch (cond[i].comp) {
					case SelCond::EQ:
						if(valEqual.size() != 0)	// this means there has been an equal condition already
						{
							multipleValEqual = true;
						}
			
						valEqual.push_back(cond[i].value);
						break;
					case SelCond::NE:
						if(valNotEqual.size() != 0)	// this means there has been an equal condition already
						{
							multipleValNotEqual = true;
						}
						hasNotEquals = true;
						valNotEqual.push_back(cond[i].value);
						break;
					case SelCond::LE:
						if(valMax == NULL || strcmp(cond[i].value, valMax) <= 0)
						{
								valMax = cond[i].value;
								valLessEqual = 1;
						}
						break;
					case SelCond::LT:
						if(valMax == NULL || strcmp(cond[i].value, valMax) < 0)
						{
							valMax = cond[i].value;
							valLessEqual = 0;
						}
						break;
					case SelCond::GE:
						if(valMin == NULL || strcmp(cond[i].value, valMin) >= 0)
						{
							valMin = cond[i].value;
							valGreaterEqual = 1;
						}
						break;
					case SelCond::GT:
						if(valMin == NULL || strcmp(cond[i].value, valMin) > 0)
						{
							valMin = cond[i].value;
							valGreaterEqual = 0;
						}
						break;
				}
				break;
		}
	}
	
	if ((idx.open(table + ".idx", 'r')) == 0) 
	{
		idxState = 1;
		
		IndexCursor ic;
		
		// Use index
		if ((keyEqual.size() != 0) || (keyMin != -1) || (keyMax != -1)) 
		{
	
			int low = keyMin;
			int high = keyMax;
			int find;
			bool valid = true;
			
			if(keyMin != -1)
			{
				find = low;
				if ((rc = idx.locate(find, ic)) != 0) 
				{
					idx.close();
					return rc;
				}
			}
			else if(keyMax != -1) 
			{
				find = high;
				if((rc = idx.locate(0, ic)) != 0) 
				{
					idx.close();
					return rc;
				}
			}
			
			if(keyEqual.size() > 0)
			{
				isEquals = true;
				find = keyEqual[0];
				for(int i = 0; i < keyEqual.size(); i++)
				{
					if(find != keyEqual[i])
						valid = false;
					find = keyEqual[i];
				}
				
				// These take care of cases where the equals is out of the low and high range
				
				if(low != -1)
				{
					if(keyGreaterEqual == 0 && find <= low)
						valid = false;
					if(keyGreaterEqual == 1 && find < low)
						valid = false;
				}
				if(high != -1)
				{
					if(keyLessEqual == 0 && find >= high)
						valid = false;
					if(keyLessEqual == 1 && find > high)
						valid = false;
				}
				if ((rc = idx.locate(find, ic)) != 0) 
				{
					idx.close();
					return rc;
				}
			}
			
			
			while (idx.readForward(ic, key, rid) == 0 && valid == true )
			{		   		
			//	cout << " in read forward" << endl;
				bool keyNE = false;
				bool keyE = false;
				if(isEquals && (key > find))		// this means we past the find location
					break;
				if((keyMin != -1) && ((keyGreaterEqual==1)?(key < low):(key <= low))) 
					continue;
				if((keyMax != -1) && ((keyLessEqual==1)?(key > high):(key >= high)))	// past the max we are supposed to be searching
					break;
				if(keyEqual.size() > 0)						// if not equal to an equal key, continue to next entry
				{
					for(int i = 0; i < keyEqual.size(); i++)
					{
						if(keyEqual[i] != key) 
							keyE = true;
					}
				}
				if(keyE == true)
					continue;
				
				
				if(keyNotEqual.size() > 0)						// if equal to a not equal key, continue to next entry
				{
					for(int i = 0; i < keyNotEqual.size(); i++)
					{
						if(keyNotEqual[i] == key) 
							keyNE = true;
					}
				}
				if(keyNE == true)
					continue;
				
				if ((rc = rf.read(rid, key, value)) != 0) {
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					idx.close();
					return rc;
				}

				
				if(valMin != NULL)
				{
					if(valGreaterEqual == 0)
					{
						if(strcmp(value.c_str(),valMin) <= 0)
						   continue;
					}
					else if(valGreaterEqual == 1)
					{
						if(strcmp(value.c_str(),valMin) < 0)
							continue;
					}
		
				}
				if(valMax != NULL)
				{
					if(valLessEqual == 0)
					{
						if(strcmp(value.c_str(),valMax) >= 0)
							continue;
					}
					else if(valLessEqual == 1)
					{
						if(strcmp(value.c_str(),valMax) > 0)
							continue;
					}
					
				}
				bool valueEq = true;
				for(int i = 0; i < valEqual.size(); i++)		// if not equal to a specified condition
				{
					if(strcmp(value.c_str(), valEqual[i]) != 0)
						valueEq = false;
				}
				if(valueEq == false)
					continue;
				
				bool valueNotEq = true;
				for(int i = 0; i < valNotEqual.size(); i++)		// if equal to a specified condition
				{
					if(strcmp(value.c_str(), valNotEqual[i]) == 0)
						valueNotEq = false;
				}
				if(valueNotEq == false)
					continue;
				
				
				// the condition is met for the tuple. 
				// increase matching tuple counter
				count++;
				
				// print the tuple 
				switch (attr) {
					case 1:  // SELECT key
						fprintf(stdout, "%d\n", key);
						break;
					case 2:  // SELECT value
						fprintf(stdout, "%s\n", value.c_str());
						break;
					case 3:  // SELECT *
						fprintf(stdout, "%d '%s'\n", key, value.c_str());
						break;
				}
				if(isEquals && key == find) break;

			}
		}
		
		if((isEquals) || (keyMin != -1) || (keyMax != -1))
		{
			if(attr == 4) {
				fprintf(stdout, "%d\n", count);
			}
		}
		
		if(!(isEquals || (keyMin != -1) || (keyMax != -1)))
		{
			idx.close();
			goto tablescan;
		}
		
		idx.close();
	}
	
	//end B+ tree index code
	if(idxState == 0) 
	{
		// scan the table file from the beginning
	tablescan:
		rid.pid = rid.sid = 0;
		count = 0;
		while (rid < rf.endRid()) {
			// read the tuple
			if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
			
			bool keyE = false;
			bool keyNE = false;
			bool valueEq = true;
			bool valueNotEq = true;
			/*
			
			// check the conditions on the tuple
			if (keyEqual != NULL && (int) key != atoi(keyEqual->value))	goto next_tuple;
			if (keyMax != NULL && (int) key >= atoi(keyMax->value))	goto next_tuple;
			if (keyMin != NULL && (int) key <= atoi(keyMin->value))	goto next_tuple;
			if (keyNotEqual && (int) key == atoi(keyNotEqual->value)) goto next_tuple;
			
			if (valEqual != NULL && value != valEqual->value)		goto next_tuple;
			if (valMax != NULL &&  value >= valMax->value)	goto next_tuple;
			if (valMin != NULL && value <= valMin->value)	goto next_tuple;
			if (valNotEqual && value == valNotEqual->value) goto next_tuple;
			
			*/
			
	
			if((keyMin != -1) && ((keyGreaterEqual==1)?(key < keyMin):(key <= keyMin))) 
				goto next_tuple;
			if((keyMax != -1) && ((keyLessEqual==1)?(key > keyMax):(key >= keyMax)))	// past the max we are supposed to be searching
				goto next_tuple;
			
			if(keyEqual.size() > 0)						// if not equal to an equal key, go to next tuple
			{
				for(int i = 0; i < keyEqual.size(); i++)
				{
					if(keyEqual[i] != key) 
						keyE = true;
				}
			}
			if(keyE == true)
				goto next_tuple;
			
			if(keyNotEqual.size() > 0)						// if equal to a not equal key, go to next tuple
			{
				for(int i = 0; i < keyNotEqual.size(); i++)
				{
					if(keyNotEqual[i] == key) 
						keyNE = true;
				}
			}
			if(keyNE == true)
				goto next_tuple;
			
			
			if(valMin != NULL)
			{
				if(valGreaterEqual == 0)
				{
					if(strcmp(value.c_str(),valMin) <= 0)
						goto next_tuple;
				}
				else if(valGreaterEqual == 1)
				{
					if(strcmp(value.c_str(),valMin) < 0)
						goto next_tuple;
				}
				
			}
			if(valMax != NULL)
			{
				if(valLessEqual == 0)
				{
					if(strcmp(value.c_str(),valMax) >= 0)
						goto next_tuple;
				}
				else if(valLessEqual == 1)
				{
					if(strcmp(value.c_str(),valMax) > 0)
						goto next_tuple;
				}
				
			}
			
			for(int i = 0; i < valEqual.size(); i++)		// if not equal to a specified condition
			{
				if(strcmp(value.c_str(), valEqual[i]) != 0)
					valueEq = false;
			}
			if(valueEq == false)
				goto next_tuple;
			
			
			for(int i = 0; i < valNotEqual.size(); i++)		// if equal to a specified condition
			{
				if(strcmp(value.c_str(), valNotEqual[i]) == 0)
					valueNotEq = false;
			}
			if(valueNotEq == false)
				goto next_tuple;
			
			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;
			
			// print the tuple 
			switch (attr) {
				case 1:  // SELECT key
					fprintf(stdout, "%d\n", key);
					break;
				case 2:  // SELECT value
					fprintf(stdout, "%s\n", value.c_str());
					break;
				case 3:  // SELECT *
					fprintf(stdout, "%d '%s'\n", key, value.c_str());
					break;
			}
			
			// move to the next tuple
		next_tuple:
			++rid;
		}
		
		// print matching tuple count if "select count(*)"
		if (attr == 4) {
			fprintf(stdout, "%d\n", count);
		}
		rc = 0;
		
		// close the table file and return
	exit_select:
		rf.close();
	}
	return rc;
}


RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	/* your code here */

	string line;
	string table_name = table + ".tbl";
	int key;
	string value;
	RecordFile rf;
	BTreeIndex b;							// create an index
	rf.open(table_name,'w');
	if(index == true)
	{
		string index_name = table + ".idx";		// the table name .idx is the name of the index to be created
		b.open(index_name, 'w');				// open the index_name in write mode since we are going to insert tuples
	}
	
	ifstream infile(loadfile.c_str());
	if (infile.is_open())
	{
		while (getline (infile,line))
		{
			if(parseLoadLine(line, key, value) != 0)		// see if we can successully parse and read the line
				return -1;
			const string cvalue = value;
			RecordId ri;
			
			if(rf.append((int)key, cvalue, ri) == 0)
			{
			
				if(index == true)					// if we created an index, insert the RecordId's with the Keys
				{
					
					if(b.insert(key, ri) != 0)
					{
					//	cout << "cannot insert into index" << endl;
					
						return RC_FILE_WRITE_FAILED;
					}
					//cout << "inserted into index" << endl;
				}
			}
			else
			{
				return -1;		// if can't append the record file, return an error
			}

		}
		infile.close();
		if(index == true)			// if we created an index, have to close it afterwards
			b.close();
	}
	
	
	rf.close();
	
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }
	
    // get the integer key value
    key = atoi(s);
	
    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }
	
    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }
	
    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }
	
    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }
	
    return 0;
}
